#include "parser.h"
#include "logger.h"
#include "util.h"

namespace syntax_analysis
{
	language* parser::do_parser( char *code )
	{
		language *lang = new language();
		syntax_entry *syn;
		string name;
		string entry;
		int line = 1;

		init();

		name = parse_language_name( code, line );
		if( name == "" )
		{
			logger::error( "language name unspecified.", code, line );
			goto ON_ERROR;
		}

		take_space( code, line );

		entry = parse_language_entry( code, line );
		if( entry == "" )
		{
			logger::error( "language entry unspecified.", code, line );
			goto ON_ERROR;
		}

		take_space( code, line );
		do 
		{
			syn = parse_entry( code, line, lang );
			if( syn != NULL )
			{
				lang->add_syntax( syn );
			}
			else
			{
				logger::error( "error occur while parsing syntax declaration", code, line );
				goto ON_ERROR;
			}
			take_space( code, line );
		} while( syn != NULL && *code != '\0' );

		lang->set_name( name );
		lang->set_entry( entry );

		if( !lang->check_integrity() )
		{
			logger::error( "check integrity failed.", code, line );
			goto ON_ERROR;
		}

		return lang;
ON_ERROR:
		delete lang;
		return NULL;
	}
	
	string parser::parse_language_name( char* &code, int &line )
	{
		char *ptr = code;
		int l = line;

		if( strncmp( ptr, "language", 8 ) != 0 )
		{
			logger::error( "keyword [language] is needed for declaring language name.", ptr, l );
			return "";
		}
		ptr += 8;

		take_space( ptr, l );

		if( strncmp( ptr, "=", 1 ) != 0 )
		{
			logger::error( "= is needed for declaring language name.", ptr, l );
			return "";
		}
		ptr += 1;

		take_space( ptr, l );

		string name = parse_string( ptr );
		if( name == "" )
		{
			logger::error( "language name is needed.", ptr, l );
			return "";
		}
		else
		{
			code = ptr;
			line = l;
			return name;
		}
	}

	string parser::parse_language_entry( char* &code, int &line )
	{
		char *ptr = code;
		int l = line;

		if( strncmp( ptr, "entry", 5 ) != 0 )
		{
			logger::error( "keyword [entry] is needed for declaring language entry.", ptr, l );
			return "";
		}
		ptr += 5;
		
		take_space( ptr, l );
		
		if( strncmp( ptr, "=", 1 ) != 0 )
		{
			logger::error( "= is needed for declaring language entry.", ptr, l );
			return "";
		}
		ptr += 1;
		
		take_space( ptr, l );
		
		string entry = parse_string( ptr );
		if( entry == "" )
		{
			logger::error( "language entry is needed.", ptr, l );
			return "";
		}
		else
		{
			code = ptr;
			line = l;
			return entry;
		}
	}

	syntax_entry* parser::parse_entry( char* &code, int &line, language *lang )
	{
		char *ptr = code;
		int l = line;

		syntax_entry *entry = NULL;
		syntax_compound *compound = NULL;
		syntax_entry *syn = NULL;
		string name;
		string on_error;

		syn = parse_syntax_head( ptr, l, name, on_error, lang );
		if( name == "" )
		{
			logger::error( "syntax name is needed in syntax declaration", ptr, l );
			goto ON_ERROR;
		}

		take_space( ptr, l );

		if( strncmp( ptr, "=", 1 ) != 0 )
		{
			logger::error( "a = should follow syntax name.", ptr, l );
			goto ON_ERROR;
		}
		ptr += 1;

		take_space( ptr, l );

		compound = parse_compound( ptr, l, lang, ';' );
		if( compound == NULL )
		{
			logger::error( "syntax declaration is missed.", ptr, l );
			goto ON_ERROR;
		}

		if( *ptr != ';' )
		{
			logger::error( "; expected.", ptr, l );
			goto ON_ERROR;
		}
		++ptr;

		if( syn != NULL )
			lang->add_syntax( syn );
		entry = new syntax_entry( name, on_error, compound );
		compound = NULL;
		code = ptr;
		line = l;
		return entry;
ON_ERROR:
		if( syn != NULL )
			delete syn;
		if( compound != NULL )
			delete compound;
		if( entry != NULL )
			delete entry;
		return NULL;
	}
	
	syntax_entry* parser::parse_syntax_head( 
		char* &code, 
		int &line,
		OUT string &name, 
		OUT string &on_error,
		language *lang )
	{
		syntax_entry *on_error_syntax = NULL;
		syntax_compound *compound = NULL;
		char *ptr = code;
		int l = line;
		
		name = parse_symbol( ptr );
		if( name == "" )
		{
			logger::error( "name is needed for syntax head.", ptr, l );
			goto ON_ERROR;
		}

		take_space( ptr, l );

		if( *ptr == '<' )
		{
			++ptr;
			take_space( ptr, l );
			if( *ptr == '>' )
			{
				logger::error( "you want to declare error goto, but none declared.", ptr, l );
				goto ON_ERROR;
			}

			on_error = parse_symbol( ptr );
			take_space( ptr, l );

			if( *ptr == '=' )
			{
				compound = parse_compound( ptr, l, lang, '>' );
				if( compound == NULL )
				{
					logger::error( "syntax declaration is missed for on error goto", ptr, l );
					goto ON_ERROR;
				}
				
				if( *ptr != '>' )
				{
					logger::error( "> expected.", ptr, l );
					goto ON_ERROR;
				}
				++ptr;

				if( on_error == "" )
				{
					char buf[64];
					snprintf( buf, 63, "unnamed_%d", util::create_uid() );
					on_error = buf;
				}
				on_error_syntax = new syntax_entry( on_error, "", compound );
				compound = NULL;
			}
			
		}

		code = ptr;
		line = l;
		return on_error_syntax;
ON_ERROR:
		if( compound != NULL )
			delete compound;
		if( on_error_syntax != NULL )
			delete on_error_syntax;
		return NULL;
	}

	syntax_compound* parser::parse_compound( char* &code, int &line, language *lang, char stop_char )
	{
		char *ptr = code;
		int l = line;
		syntax *syn = NULL;
		syntax_compound *compound = new syntax_compound();
		syntax_attribute *attr;

		if( stop_char == '\'' )
			compound->set_compound_type( COMPOUND_OR );
		else
			compound->set_compound_type( COMPOUND_AND );

		while( *ptr != '\0' && *ptr != stop_char )
		{
			take_space( ptr, l );
			switch( *ptr )
			{
			case '\"':
			case '\'':
				++ptr;
				char tmp;
				tmp = *(ptr-1);
				syn = parse_compound( ptr, l, lang, tmp );
				if( syn == NULL )
				{
					logger::error( "parse compound syntax failed.", ptr, l );
					goto ON_ERROR;
				}
				if( *ptr != tmp )
				{
					logger::error( "quotation expected.", ptr, l );
					goto ON_ERROR;
				}
				++ptr;
				
				if( *ptr == '[' || *ptr == '(' )
				{
					attr = parse_attr( ptr, l );
					if( attr == NULL )
					{
						logger::error( "parse attribute failed.", ptr, l );
						goto ON_ERROR;
					}
					((syntax_compound*)syn)->set_attr( attr );
					attr = NULL;
				}

				compound->add_child( syn );
				break;
			case '{':
				syn = parse_call( ptr, l, lang );
				if( syn == NULL )
				{
					logger::error( "parse syntax call failed.", ptr, l );
					goto ON_ERROR;
				}
				compound->add_child( syn );
				break;
			case '[':
			case ']':
			case '}':
			case '<':
			case '>':
				logger::error( "syntax expected.", ptr, l );
				goto ON_ERROR;
				break;
			case '\\':
			default:
				syn = parse_char( ptr, l );
				if( syn == NULL )
				{
					logger::error( "correct character expected.", ptr, l );
					goto ON_ERROR;
				}
				compound->add_child( syn );
				break;
			}
		}
		code = ptr;
		line = l;

		return compound;
ON_ERROR:
		if( compound != NULL )
			delete compound;
		return NULL;
	}

	syntax_char* parser::parse_char( char* &code, int &line )
	{
		syntax_char *syn = NULL;
		syntax_attribute *attr = NULL;
		char *ptr = code;
		int l = line;
		char c;

		if( *ptr == '\0' )
		{
			logger::error( "character expected.", ptr, l );
			goto ON_ERROR;
		}

		if( *ptr == '\\' )
		{
			switch( ptr[1] )
			{
			case '[':
			case ']':
			case '(':
			case ')':
			case '{':
			case '}':
			case '<':
			case '>':
			case ';':
			case '\'':
			case '\"':
			case ',':
			case '\\':
				c = ptr[1];
				break;
			case 'n':
				c = '\n';
				break;
			case 'r':
				c = '\r';
				break;
			case 't':
				c = '\t';
				break;
			case 'p':
				c = ' ';
				break;
			default:
				logger::error( "unrecognized escape character.", ptr, l );
				goto ON_ERROR;
			}
			ptr += 2;
		}
		else
		{
			c = *ptr;
			++ptr;
		}
		take_space( ptr, l );

		if( *ptr == '[' || *ptr == '(' )
		{
			attr = parse_attr( ptr, l );
			if( attr == NULL )
			{
				logger::error( "syntax attribute expected.", ptr, l );
				goto ON_ERROR;
			}
		}
		syn = new syntax_char( c, attr );
		code = ptr;
		line = l;
		
		return syn;
ON_ERROR:
		if( syn != NULL )
			delete syn;
		return NULL;
	}

	syntax_call* parser::parse_call( char* &code, int &line, language *lang )
	{
		char *ptr = code;
		int l = line;
		
		syntax_attribute *attr = NULL;
		syntax_call *call = NULL;
		syntax_compound *compound = NULL;
		syntax_entry *syn = NULL;
		string name;
		
		if( *ptr != '{' )
		{
			logger::error( "it is not a call syntax.", ptr, l );
			goto ON_ERROR;
		}
		++ptr;
		take_space( ptr, l );

		name = parse_symbol( ptr );
		if( name == "" )
		{
			char buf[64];
			snprintf( buf, 63, "unnamed_%d", util::create_uid() );
			name = buf;
		}
		
		take_space( ptr, l );
		
		if( strncmp( ptr, "=", 1 ) == 0 )
		{
			ptr += 1;
			
			take_space( ptr, l );
			
			compound = parse_compound( ptr, l, lang, '}' );
			if( compound == NULL )
			{
				logger::error( "syntax declaration is missed.", ptr, l );
				goto ON_ERROR;
			}
			
			syn = new syntax_entry( name, "", compound );
			compound = NULL;

		}
		take_space( ptr, l );

		if( *ptr == '}' )
		{
			++ptr;
			if( *ptr == '[' || *ptr == '(' )
			{
				attr = parse_attr( ptr, l );
				if( attr == NULL )
				{
					logger::error( "error attribute.", ptr, l );
					goto ON_ERROR;
				}
			}
		}
		else
		{
			logger::error( "} expected.", ptr, l );
			goto ON_ERROR;
		}


		
		if( syn != NULL )
			lang->add_syntax( syn );
		call = new syntax_call( name, attr );
		code = ptr;
		line = l;
		return call;
ON_ERROR:
		if( syn != NULL )
			delete syn;
		if( compound != NULL )
			delete compound;
		if( call != NULL )
			delete call;
		if( attr != NULL )
			delete attr;
		return NULL;
	}

	syntax_attribute* parser::parse_attr( char* &code, int &line )
	{
		syntax_attribute *attr = NULL;
		char *ptr = code;
		int l = line;
		int match_type = 0;
		int nmin = UNKNOWN;
		int nmax = UNKNOWN;
		expression *emin;
		expression *emax;
		exp_opd *omin;
		exp_opd *omax;
		

		if( *ptr == '[' )
		{
			++ptr;
			take_space( ptr, l );

			if( *ptr == '*' )
			{
				++ptr;
				nmin = EXP_ALL;
			}
			else if( *ptr == '+' )
			{
				++ptr;
				nmin = EXP_ONE_MORE;
			}
			else
			{
				nmin = parse_number( ptr );
				if( nmin < 0 )
				{
					logger::error( "number expected.", ptr, l );
					goto ON_ERROR;
				}
			}
			take_space( ptr, l );

			if( *ptr == ',' )
			{
				++ptr;
				take_space( ptr, l );
				if( *ptr == '*' )
				{
					++ptr;
					nmax = EXP_ALL;
				}
				else if( *ptr == '+' )
				{
					++ptr;
					nmax = EXP_ONE_MORE;
				}
				else
				{
					nmax = parse_number( ptr );
					if( nmax >= 0 && nmax < nmin )
					{
						logger::error( "max number should great than min number.", ptr, l );
						goto ON_ERROR;
					}
				}
			}
			take_space( ptr, l );
			if( *ptr != ']' )
			{
				logger::error( "] expected.", ptr, l );
				goto ON_ERROR;
			}
			++ptr;
		}
		if( *ptr == '(' )
		{
			++ptr;
			take_space( ptr, l );

			bool goon = true;
			while( goon )
			{
				if( strncmp( ptr, "max_match", 9 ) == 0 )
				{
					ptr += 9;
					match_type |= MAX_MATCH;
				}
				else if( strncmp( ptr, "min_match", 9 ) == 0 )
				{
					ptr += 9;
					match_type |= MIN_MATCH;
				}
				else if( strncmp( ptr, "take", 4 ) == 0 )
				{
					ptr += 4;
					match_type |= TAKE_STRING;
				}
				else if( strncmp( ptr, "dont_take", 9 ) == 0 )
				{
					ptr += 9;
					match_type |= DONT_TAKE_STRING;
				}

				take_space( ptr, l );
				if( *ptr == ',' )
				{
					goon = true;
					++ptr;
				}
				else if ( *ptr != ')' )
				{
					logger::error( ") expected.", ptr, l );
					goto ON_ERROR;
				}
				else
				{
					goon = false;
					++ptr;
				}
			}
		}

		if( nmin != UNKNOWN )
		{
			omin = new exp_opd( nmin );
			emin = new expression( OPT_NONE, omin, NULL );
		}
		else
		{
			emin = NULL;
		}
		if( nmax != UNKNOWN )
		{
			omax = new exp_opd( nmax );
			emax = new expression( OPT_NONE, omax, NULL );
		}
		else
		{
			emax = NULL;
		}
		if( ( match_type & (MIN_MATCH|MAX_MATCH) ) == 0 )
			match_type |= MAX_MATCH;
		if( ( match_type & (TAKE_STRING|DONT_TAKE_STRING) ) == 0 )
			match_type |= TAKE_STRING;
		attr = new syntax_attribute( emin, emax, match_type );
		
		code = ptr;
		line = l;
		return attr;
ON_ERROR:
		if( attr != NULL )
			delete attr;
		return NULL;
	}

	int parser::parse_number( char* &code )
	{
		int n = 0;
		while( *code >= '0' && *code <= '9' )
		{
			n *= 10;
			n += *code - '0';
			++code;
		}
		return n;
	}

	void parser::take_space( char* &code, int &line )
	{
		while( m_space[(unsigned)*code] != 0 )
		{
			if( *code == '\n' )
				++line;
			++code;
		}
	}

	string parser::parse_string( char* &code )
	{
		char buf[10240];
		int len = 0;
		char *ptr = code;
		if( *ptr == '\"' )
		{
			++ptr;
			while( *ptr != '\0' && *ptr != '\"' )
			{
				if( *ptr == '\\' )
				{
					switch( ptr[1] )
					{
					case 'n':
						buf[len++] = '\n';
						break;
					case 't':
						buf[len++] = '\t';
						break;
					case 'r':
						buf[len++] = '\r';
						break;
					case 'p':
						buf[len++] = ' ';
						break;
					default:
						buf[len++] = ptr[1];
						break;
					}
					++ptr;
				}
				else
				{
					buf[len++] = *ptr;
				}
				++ptr;
			}
			buf[len++] = 0;
			string str = buf;
			if( *ptr != '\0' )
				++ptr;
			code = ptr;
			return str;
		}
		return "";
	}

	string parser::parse_symbol( char* &code )
	{
		char buf[10240];
		int len = 0;
		char *ptr = code;
		if( m_symbol_head[(unsigned)*ptr] != 0 )
		{
			buf[len++] = *ptr;
			++ptr;
			while( m_symbol[(unsigned)*ptr] != 0 )
			{
				buf[len++] = *ptr;
				++ptr;
			}
		}
		buf[len++] = 0;
		string sym = buf;
		code = ptr;
		return sym;
	}

	void parser::init()
	{
		unsigned i;

		memset( m_space, 0, sizeof(m_space) );
		m_space[(unsigned)' '] = 1;
		m_space[(unsigned)'\t'] = 1;
		m_space[(unsigned)'\r'] = 1;
		m_space[(unsigned)'\n'] = 1;

		memset( m_symbol, 0, sizeof(m_symbol) );
		memset( m_symbol_head, 0, sizeof(m_symbol_head) );
		for( i = '0'; i <= '9'; ++i )
			m_symbol[i] = 1;
		for( i = 'a'; i <='z'; ++i )
		{
			m_symbol[i] = 1;
			m_symbol[i+'A'-'a'] = 1;
			m_symbol_head[i] = 1;
			m_symbol_head[i+'A'-'a'] = 1;
		}
		m_symbol[(unsigned)'_'] = 1;
		m_symbol_head[(unsigned)'_'] = 1;
		m_symbol[(unsigned)'$'] = 1;
		m_symbol_head[(unsigned)'$'] = 1;
	}
}